//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <poll.h>

#include <aura.h>
#include <auraplus.h>
#include <aura_wchar.h>

#define QLEN    64
#define STALE   30  /* client's name can't be older than this (sec) */
#define CLIENT_PATH    "/var/tmp/"      /* +5 for pid = 14 chars */
#define CLIENT_PERM    S_IRWXU          /* rwx for user only */

static Aura_Int32 server_listen(const char *name);
static Aura_Int32 server_accept(Aura_Int32 listenFd, uid_t *uidptr);
static Aura_Int32 client_connect(const char *name);

/*
 * Create a server endpoint of a connection.
 * Returns fd if all OK, <0 on error.
 */
static Aura_Int32 server_listen(const char *name)
{
    Aura_Int32 fd, len, err, rval;
    struct sockaddr_un  un;

    /* create a UNIX domain stream socket */
    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
        return(-1);
    unlink(name);   /* in case it already exists */

    /* fill in socket address structure */
    memset(&un, 0, sizeof(un));
    un.sun_family = AF_UNIX;
    strcpy(un.sun_path, name);
    len = offsetof(struct sockaddr_un, sun_path) + strlen(name);

    /* bind the name to the descriptor */
    if (bind(fd, (struct sockaddr *)&un, len) < 0) {
        rval = -2;
        goto Exit;
    }
    if (listen(fd, QLEN) < 0) { /* tell kernel we're a server */
        rval = -3;
        goto Exit;
    }
    return(fd);

Exit:
    err = errno;
    close(fd);
    errno = err;
    return(rval);
}

/*
 * Wait for a client connection to arrive, and accept it.
 * We also obtain the client's user ID from the pathname
 * that it must bind before calling us.
 * Returns new fd if all OK, <0 on error
 */
static Aura_Int32 server_accept(Aura_Int32 listenFd, uid_t *uidptr)
{
    Aura_Int32 clientFd, len, err, rval;
    time_t staletime;
    struct sockaddr_un un;
    struct stat statbuf;

    len = sizeof(un);
    if ((clientFd = accept(listenFd, (struct sockaddr *)&un, (socklen_t*)&len)) < 0)
        return(-1);     /* often errno=EInt32R, if signal caught */

    /* obtain the client's uid from its calling address */
    len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */
    un.sun_path[len] = 0;           /* null terminate */

    if (stat(un.sun_path, &statbuf) < 0) {
        rval = -2;
        goto Exit;
    }
#ifdef S_ISSOCK     /* not defined for SVR4 */
    if (S_ISSOCK(statbuf.st_mode) == 0) {
        rval = -3;      /* not a socket */
        goto Exit;
    }
#endif
    if ((statbuf.st_mode & (S_IRWXG | S_IRWXO)) ||
        (statbuf.st_mode & S_IRWXU) != S_IRWXU) {
        rval = -4;     /* is not rwx------ */
        goto Exit;
    }

    staletime = time(AURA_NULL) - STALE;
    if (statbuf.st_atime < staletime ||
        statbuf.st_ctime < staletime ||
        statbuf.st_mtime < staletime) {
        rval = -5;    /* i-node is too old */
        goto Exit;
    }
    if (uidptr != AURA_NULL)
        *uidptr = statbuf.st_uid;   /* return uid of caller */
    unlink(un.sun_path);        /* we're done with pathname now */
    return(clientFd);

Exit:
    err = errno;
    close(clientFd);
    errno = err;
    return(rval);
}

/*
 * Create a client endpoint and connect to a server.
 * Returns fd if all OK, <0 on error.
 */
static Aura_Int32 client_connect(const char *name)
{
    Aura_Int32 fd, len, err, rval;
    struct sockaddr_un un;

    /* create a UNIX domain stream socket */
    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
        return(-1);

    /* fill socket address structure with our address */
    memset(&un, 0, sizeof(un));
    un.sun_family = AF_UNIX;
    sprintf(un.sun_path, "%s%05d", CLIENT_PATH, getpid());
    len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);

    unlink(un.sun_path);        /* in case it already exists */
    if (bind(fd, (struct sockaddr *)&un, len) < 0) {
        rval = -2;
        goto Exit;
    }
    if (chmod(un.sun_path, CLIENT_PERM) < 0) {
        rval = -3;
        goto Exit;
    }
    /* fill socket address structure with server's address */
    memset(&un, 0, sizeof(un));
    un.sun_family = AF_UNIX;
    strcpy(un.sun_path, name);
    len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
    if (connect(fd, (struct sockaddr *)&un, len) < 0) {
        rval = -4;
        goto Exit;
    }

    return(fd);

Exit:
    err = errno;
    close(fd);
    errno = err;

    return(rval);
}

AURA_API AuraIpcChannel_Create(
    /* [in] */ Aura_PWChar chanelName,
    /* [out] */ Aura_ChannelId * pListenId)
{
    Aura_Int32 listenFd;
    struct pollfd *pollfd = AURA_NULL;
    char serverChannelName[AURA_MAX_PATH];

    UnicToAnsi(serverChannelName, chanelName);
    // wcstombs(serverChannelName, chanelName, AURA_MAX_PATH);

    if ((pollfd = (struct pollfd*)malloc(sizeof(struct pollfd))) == AURA_NULL) {
        PERROR("malloc error\n");
        return AURA_E_FAIL;
    }

    /* obtain fd to listen for client requests on */
    if ((listenFd = server_listen(serverChannelName)) < 0) {
        PERROR("server_listen error\n");
        return AURA_E_FAIL;
    }

    pollfd->fd = listenFd;
    pollfd->events = POLLIN;

    *pListenId = (Aura_ChannelId)pollfd; // use pListenId to store the pollfd array.

    return AURA_NOERROR;
}

AURA_API AuraIpcChannel_Destroy(
    /* [in] */ Aura_ChannelId channelId)
{
    struct pollfd *pollfd = ( struct pollfd *)channelId;

    assert(pollfd);

	close(pollfd->fd);
	free(pollfd);

    return 0;
}

AURA_API AuraIpcChannel_WaitForConnection(
    /* [in] */ Aura_PWChar channelName,
    /* [out] */ Aura_ChannelId * pListenedId,
    /* [out] */ Aura_ChannelId * pChannelId)
{
    Aura_Int32 clientFd;
    uid_t uid = 0;
    struct pollfd *pollfd = (struct pollfd*)(*pListenedId);
    // pListenedId is used to represent the pollfd struct array.

    if (poll(pollfd, 1, -1) < 0) {
        PERROR("poll error\n");
        return AURA_E_FAIL;
    }

    if (pollfd[0].revents & POLLIN) {  /* accept new client request */
        if ((clientFd = server_accept(pollfd[0].fd, &uid)) < 0) {
            PERROR("server_accept error: %d\n", clientFd);
            return AURA_E_FAIL;
        }

        *pChannelId = clientFd;
        PTRACE("IpcServerWaitForRequest new connection: uid %d, fd %d\n", uid, clientFd);
        return AURA_NOERROR;
    }

    return AURA_E_FAIL;
}

AURA_API AuraIpcChannel_Read(
    /* [in] */ Aura_ChannelId channelId,
    /* [in] */ Aura_PVoid pBuffer,
    /* [in] */ Aura_UInt32 bufferSize)
{
    Aura_Int32 nread;
    Aura_Int32 clientFd = (Aura_Int32)channelId;

//    ENTRY("IpcServerReadRequest id:%d, pvBuf:0x%x, uBufSize:%d\n",
//            channelId, pBuffer, bufferSize);

    if ((nread = read(clientFd, pBuffer, bufferSize)) < 0) {
        PERROR("Read error on fd %d, errno:%d", clientFd, errno);
        return AURA_E_FAIL;
    }
    else if (nread == 0) {
        PTRACE("Aura_ChannelId %d Closed\n", clientFd);
        close(clientFd);
        return AURA_E_FAIL;
    }

//    LOGEXIT("IpcServerReadRequest\n");

    return AURA_NOERROR;
}

AURA_API AuraIpcChannel_AsyncWrite(
    /* [in] */ Aura_ChannelId channelId,
    /* [in] */ Aura_PVoid pBuffer,
    /* [in] */ Aura_UInt32 bufferSize)
{
    Aura_Int32 serverFd = (Aura_Int32)channelId;

//    ENTRY("AuraIpcChannel_AsyncWrite channelId:%d, pBuffer:0x%x, bufferSize:%d\n",
//        channelId, pBuffer, bufferSize);

    if (write(serverFd, pBuffer, bufferSize) != (Aura_Int32)bufferSize) {
        PERROR("Write error on fd %d, errno:%d", serverFd, errno);
        return AURA_E_FAIL;
    }

//    LOGEXIT("AuraIpcChannel_AsyncWrite\n");

    return AURA_NOERROR;
}

AURA_API AuraIpcChannel_Connect(
    /* [in] */ Aura_PWChar channelName,
    /* [out] */ Aura_ChannelId * pChannelId)
{
    char clientChannelName[AURA_MAX_PATH];


    UnicToAnsi(clientChannelName, channelName);
    // wcstombs(clientChannelName, chanelName, AURA_MAX_PATH);
    if ((*pChannelId = client_connect(clientChannelName)) < 0) {
        PERROR("client_connect error, errno:%d!\n", errno);
        return AURA_E_FAIL;
    }

    return AURA_NOERROR;
}

AURA_API AuraIpcChannel_Disconnect(
    /* [in] */ Aura_ChannelId channelId)
{
    close((Aura_Int32)channelId);

    return AURA_NOERROR;
}

AURA_API AuraIpcChannel_SyncWrite(
    /* [in] */ Aura_ChannelId channelId,
    /* [in] */ Aura_PVoid pInBuffer,
    /* [in] */ Aura_UInt32 inBufferSize,
    /* [out] */ Aura_PVoid pOutBuffer,
    /* [in] */ Aura_UInt32 outBuferSize)
{
    Aura_Int32 nread;
    Aura_Int32 serverFd = (Aura_Int32)channelId;

    if (write(serverFd, pInBuffer, inBufferSize) != (Aura_Int32)inBufferSize) {
        PERROR("Write error on fd %d, errno:%d\n", serverFd, errno);
        return AURA_E_FAIL;
    }

    if ((nread = read(serverFd, pOutBuffer, outBuferSize)) < 0) {
        PERROR("Read error on fd %d, errno:%d\n", serverFd, errno);
        return AURA_E_FAIL;
    }
    else if (nread == 0) {
        PTRACE("closed: fd %d\n", serverFd);
        close(serverFd);
        return AURA_E_FAIL;
    }

    return AURA_NOERROR;
}
